import enum
import re
from serial import Serial, Timeout  # need pyserial
from .sinocmd import (
    TO1,  # switch to com1
    TO2,  # switch to com2
    ECI,  # ec20 command begin
    ECO,  # ec20 command end
    LSN,  # log sn
    LRS,  # log id
    LID,  # log id too
    LFR,  # log function registry
    M_U,  # m-series user
    M_P,  # m-series password
    M_E,  # m-series logout
    A_U,  # a-series user
    A_P,  # a-series password
    A_E,  # a-series logout
    ST2,  # setting tool with 2 parameters
    ST3,  # setting tool with 3 parameters
    ST4,  # setting tool for a-series m01
    ST5,  # setting tool for a-series m02
    P_I,  # id return pattern
    P_N,  # sn return pattern
    P_T,  # setting tool return pattern
    P_L,  # function registry target line pattern
    P_E,  # function registry ending line pattern
    P_U,  # m-series user prompt pattern
    P_P,  # m-series password prompt pattern
    P_S,  # m-series shell prompt pattern
)
from typing import override


class SinoDevice(Serial):
    def __init__(
        self, port: str, baudrate: int = 115200, timeout: float = 0.5, **kwargs
    ):
        super().__init__(port=port, baudrate=baudrate, timeout=timeout, **kwargs)
        self._sn: str = ""
        self._sn_length: int = 0
        self._hwv: str = ""
        self._hwv_length: int = 0

    @property
    def sn(self) -> str:
        return self._sn

    @property
    def sn_length(self) -> int:
        return self._sn_length

    @sn_length.setter
    def sn_length(self, length: int):
        self._sn_length = length

    @property
    def is_valid_sn(self) -> bool:
        return self._sn_length == len(self._sn)

    @property
    def hwv(self) -> str:
        return self._hwv

    @property
    def hwv_length(self) -> int:
        return self._hwv_length

    @hwv_length.setter
    def hwv_length(self, length: int):
        self._hwv_length = length

    @property
    def is_valid_hwv(self) -> bool:
        return self._hwv_length == len(self._hwv)

    @override
    def write(self, *commands: str, eol="\r\n", encoding="iso-8859-1") -> int | None:
        if not commands:
            return super().write(eol.encode(encoding))
        elif len(commands) == 1:
            return super().write((commands[0] + eol).encode(encoding))
        else:
            return super().write((eol.join(commands) + eol).encode(encoding))

    @override
    def read_until(
        self,
        ending_pattern: str | re.Pattern[str] = r"\n",
        target_pattern: str | re.Pattern[str] | None = None,
        encoding="iso-8859-1",
    ) -> list:
        """Returns list[str] or list[tuple[str]] based on `target_pattern`."""
        decoded_paragraph = ""
        timeout = Timeout(self.timeout)  # type: ignore
        while not timeout.expired():
            line = super().read_until(expected=b"\n")
            decoded_line = line.decode(encoding)
            decoded_paragraph += decoded_line
            if re.findall(ending_pattern, decoded_paragraph):
                break
        if target_pattern:
            return re.findall(target_pattern, decoded_paragraph)
        else:
            return [decoded_paragraph]


class SinoDeviceType(enum.Enum):
    RTK = "rtk"
    SLAM = "slam"
    RS = "rs"
    MS = "ms"


class SinoRTKCompatibility(enum.Enum):
    DEFAULT = 0
    EC20WRITE = 1
    EC20REWRITE = 2


class SinoRTK(SinoDevice):
    def __init__(
        self, port: str, baudrate: int = 115200, timeout: float = 0.5, **kwargs
    ):
        super().__init__(port=port, baudrate=baudrate, timeout=timeout, **kwargs)
        self._id: str = ""
        self._fr: list[str] = []

    @property
    def id(self) -> str:
        return self._id

    @property
    def fr(self) -> list[str]:
        """Function Registry"""
        return self._fr

    def fill_id(self, try_times: int = 4) -> bool:
        for _ in range(try_times):
            self.write(TO1, LRS, LID)
            if msg := self.read_until(P_I, P_I):
                self._id = msg[0]
                return True
        return False

    def fill_fr(self, try_times: int = 4) -> bool:
        for _ in range(try_times):
            self.write(TO1, LFR)
            if curr_fr := self.read_until(P_E, P_L):
                self._fr = curr_fr
                return True
        return False

    def fill_sn_hwv(self, try_times: int = 4) -> bool:
        p_n = P_N.format(sn_length=self._sn_length, hwv_length=self._hwv_length)
        for _ in range(try_times):
            self.write(TO2, LSN)
            if msg := self.read_until(p_n, p_n):
                self._sn, self._hwv = msg[0]
                return True
        return False

    def fill(self, try_times: int = 4) -> bool:
        return (
            self.fill_id(try_times)
            and self.fill_fr(try_times)
            and self.fill_sn_hwv(try_times)
        )

    def set_sn_hwv(
        self,
        sn: str,
        hwv: str,
        compatibility: SinoRTKCompatibility = SinoRTKCompatibility.DEFAULT,
        try_times: int = 4,
    ) -> bool:
        p_t = P_T.format(hwv_length=self._hwv_length, sn_length=self._sn_length)
        for _ in range(try_times):
            match compatibility:
                case SinoRTKCompatibility.DEFAULT:
                    self.write(TO2, ST2.format(sn=sn, hwv=hwv))
                case SinoRTKCompatibility.EC20WRITE:
                    self.write(TO2, ECI, ST2.format(sn=sn, hwv=hwv))
                case SinoRTKCompatibility.EC20REWRITE:
                    self.write(TO2, ECI, ST2.format(sn=sn, hwv=hwv), ECO)
            if msg := self.read_until(p_t, p_t):
                self._hwv, self._sn = msg[0]
                return True
        return False

    def regist(self, regcode: str, try_times: int = 4) -> bool:
        last_fr = self._fr
        for _ in range(try_times):
            self.write(TO1, regcode)
            if self.read_until(P_E, P_L) != last_fr:
                return True
        return False


class SinoSLAM(SinoRTK):
    pass


class SinoRS(SinoDevice):
    """SinoGNSS Reference Station - M Series"""

    def __init__(
        self, port: str, baudrate: int = 115200, timeout: float = 0.5, **kwargs
    ):
        super().__init__(port=port, baudrate=baudrate, timeout=timeout, **kwargs)
        self._mac: str = ""

    @property
    def mac(self) -> str:
        return self._mac

    def login(self):
        self.write()
        self.read_until(P_U)
        self.write(M_U)
        self.read_until(P_P)
        self.write(M_P)
        self.read_until(P_S)

    def logout(self):
        self.write(M_E)

    def set_sn_hwv_mac(self, sn: str, hwv: str, mac: str, try_times: int = 4) -> bool:
        p_t = P_T.format(hwv_length=self._hwv_length, sn_length=self._sn_length)
        for _ in range(try_times):
            self.write(ST3.format(sn=sn, hwv=hwv, mac=mac))
            if msg := self.read_until(p_t, p_t):
                self._hwv, self._sn = msg[0]
                self._mac = mac
                return True
        return False


class SinoMSCompatibility(enum.Enum):
    DEFAULT = 0
    M01 = 1
    M02 = 2


class SinoMS(SinoDevice):
    """SinoGNSS Monitor Station - A Series"""

    @property
    def sn(self) -> str:
        return self._sn

    @property
    def hwv(self) -> str:
        return self._hwv

    def login(self):
        self.write()
        self.read_until(P_U)
        self.write(A_U)
        self.read_until(P_P)
        self.write(A_P)
        self.read_until(P_S)

    def logout(self):
        self.write(A_E)

    def set_sn_hwv(
        self,
        sn: str,
        hwv: str,
        compatibility: SinoMSCompatibility = SinoMSCompatibility.DEFAULT,
        try_times: int = 4,
    ) -> bool:
        p_t = P_T.format(hwv_length=self._hwv_length, sn_length=self._sn_length)
        match compatibility:
            case SinoMSCompatibility.DEFAULT:
                stl = ST2.format(sn=sn, hwv=hwv)
            case SinoMSCompatibility.M01:
                stl = ST4.format(sn=sn, hwv=hwv)
            case SinoMSCompatibility.M02:
                stl = ST5.format(sn=sn, hwv=hwv)
        for _ in range(try_times):
            self.write(stl)
            if msg := self.read_until(p_t, p_t):
                self._hwv, self._sn = msg[0]
                return True
        return False
